---
title: EAS Update with a local build
sidebar_title: Build locally
description: Learn how to use EAS Update directly with a local build environment.
---

import { ContentSpotlight } from '~/ui/components/ContentSpotlight';
import { Terminal } from '~/ui/components/Snippet';
import { Step } from '~/ui/components/Step';

In this step-by-step guide, you'll create a new Expo app, run it in your local development environment, publish an update, and show that the app downloads and runs the update when restarted. It also details some of the app config properties used in EAS Update and their functions.

<Step label="1">
## Install the latest EAS CLI

EAS CLI is the command-line app that you will use to interact with EAS services from your terminal. To install it, run the command:

<Terminal cmd={['$ npm install --global eas-cli']} />

You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version.

> We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation.

</Step>

<Step label="2">
## Create a project

Create a project by running:

<Terminal cmd={['$ npx create-expo-app']} />
</Step>

<Step label="3">
## Configure your project

To configure your project, run the following commands in the order they are specified:

<Terminal
  cmd={[
    '# Install the latest `expo-updates` library',
    '$ npx expo install expo-updates',
    '',
    '# Initialize the app in EAS',
    '$ eas init',
    '',
    '# Initialize your project with EAS Update',
    '$ eas update:configure',
  ]}
  cmdCopy="npx expo install expo-updates && eas init && eas update:configure"
/>

Inside the app config, make changes to use [`runtimeVersion`](/versions/latest/config/app/#runtimeversion) property with a fixed value and to add a request header that the app will use to specify a channel when downloading updates from EAS.

{/* prettier-ignore */}
```json app.json
  "expo": {
    /* @hide ... */ /* @end */
    "updates": {
      /* @hide ... */ /* @end */
      "requestHeaders": {
        "expo-channel-name": "main"
      }
      /* @hide ... */ /* @end */
    }
    "runtimeVersion": "1.0.0",
    /* @hide ... */ /* @end */
  }
```

> In the [Get started](/eas-update/getting-started) guide, the channel name is set by EAS Build and is different for various build profiles and automatically synchronized on the server. Since this is a local build, it is necessary to set the request header explicitly in the app config, as shown above, and then manually create channel on the server.

<Terminal
  cmd={[
    '# Create the `main` channel on EAS (which points it to the main EAS Update branch by default)',
    '$ eas channel:create main',
  ]}
  cmdCopy="eas channel:create main"
/>

</Step>

<Step label="4">
## Run prebuild

Prebuild runs automatically when executing `npx expo run:android` or `npx expo run:ios`. However, we want to call this step out separately in this guide. From SDK 48 and above, this step adds our desired request headers to **AndroidManifest.xml** and **Expo.plist** in the Android and iOS builds.

<Terminal
  cmd={[
    '# Run prebuild to install and configure the android and ios directories, and install CocoaPods for iOS.',
    '$ npx expo prebuild',
  ]}
  cmdCopy="npx expo prebuild"
/>

After running the above, inspect **android/app/src/main/AndroidManifest.xml** and **iOS/&lt;your project name&gt;/Supporting/Expo.plist** to see the added changes.

{/* prettier-ignore */}
```xml Expo.plist
<!-- @hide ... Existing configuration --> <!-- @end -->
  <!-- @info Changes added to Expo.plist on running prebuild command. -->
  <key>EXUpdatesRequestHeaders</key>
  <dict>
    <key>expo-channel-name</key>
    <string>main</string>
  </dict>
  <!-- @end -->
<!-- @hide ... Existing configuration --><!-- @end -->
```

{/* prettier-ignore */}
```xml AndroidManifest.xml
<!-- @hide ... Existing configuration --> <!-- @end -->
  <!-- @info Changes added to AndroidManifest.xml on run running prebuild command. --> <meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value="{&quot;expo-channel-name&quot;:&quot;main&quot;}"/> <!-- @end -->
<!-- @hide ... Existing configuration --><!-- @end -->
```

> In SDK 48 and later, the [`requestHeaders`](/versions/latest/config/app/#requestheaders) property from the app config is used to automatically add these lines to the native files.

</Step>

<Step label="5">
## Build the project

Run the following commands to get the app running on Android or iOS.

<Terminal
  cmd={[
    '# Android: Build and run the app on a local Android Emulator',
    '$ npx expo run:android --variant release',
    '',
    '# iOS: Build and run the app on a local iOS Simulator.',
    '$ npx expo run:ios --configuration Release',
  ]}
  cmdCopy="npx expo run:android --variant release && npx expo run:ios --configuration Release"
/>

We build the release version of the app, not the debug version. By default, a debug build expects to connect to a local React Native packager to load the application bundle and will not connect to the EAS Update service.

It is possible to create a **native debug** build that will connect to the EAS Update service and allow debugging of native code. See [Testing updates with a debug build](#testing-updates-with-a-debug-build-of-your-app) section for the required changes.

</Step>

<Step label="6">

Make the modifications below to **App.js** so that the app uses the `Updates` API to show if it has downloaded and run an update.

{/* prettier-ignore */}
``` jsx App.js
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
/* @info New */ import * as Updates from 'expo-updates'; /* @end */

export default function App() {
  /* @info New */ const runTypeMessage = Updates.isEmbeddedLaunch /* @end */
  /* @info New */   ? 'This app is running from built-in code' /* @end */
  /* @info New */   : 'This app is running an update'; /* @end */
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      /* @info New */ <Text>{runTypeMessage}</Text> /* @end */
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
```

Then build and run the app again:

<Terminal
  cmd={[
    '# Android: Build and run the app on a local Android Emulator',
    '$ npx expo run:android --variant release',
    '',
    '# iOS: Build and run the app on a local iOS Simulator.',
    '$ npx expo run:ios --configuration Release',
  ]}
  cmdCopy="npx expo run:android --variant release && npx expo run:ios --configuration Release"
/>

The app should appear as in the screenshot below.

<ContentSpotlight
  alt="App running for the first time"
  src="/static/images/eas-update/getting-started-before-update.png"
  className="max-w-[280px]"
  />
</Step>

<Step label="7">

## Publish an update

Now we're ready to publish an update that will include our changes.

After optionally making additional changes to **App.js**, publish an update with the following command:

<Terminal cmd={['$ eas update']} />

Running the command will prompt the following:

- For the branch to publish on. Select the default branch: `main`. This is the branch that the `main` channel was linked to by default above.
- For entering an update message. Select the default `Initial commit` message or type in a custom message.

Once the update is built and uploaded to EAS and the command completes, force close and reopen your app up to two times to download and view the update.

The app should now appear as shown in the screenshot below:

<ContentSpotlight
  alt="App running update after being restarted twice"
  src="/static/images/eas-update/getting-started-after-update.png"
  className="max-w-[280px]"
/>

</Step>

## Testing updates with a debug build of your app

It is possible to [create a debug build that will load updates from EAS instead of loading from a React Native packager](/eas-update/debug-advanced/#debugging-of-native-code-while-loading-the-app-through-expo-updates).

To do this for the above app, modify step 4 above as follows:

<Terminal
  cmd={[
    '# Set the environment variable needed for native debug builds',
    '$ export EX_UPDATES_NATIVE_DEBUG=1',
    '',
    '# Run prebuild to install and configure the android and ios directories, and install CocoaPods for iOS.',
    '$ npx expo prebuild',
    '',
    '# iOS: Modify the application project so that the application JavaScript',
    '',
    '# is bundled into the app for both debug and release builds.',
    "$ sed -i '' 's/SKIP_BUNDLING/FORCE_BUNDLING/g;' ios/<project name>.xcodeproj/project.pbxproj",
    '',
    '# iOS: Build and run the app on a local iOS Simulator.',
    '$ npx expo run:ios --configuration Debug',
    '',
    '# Android: Build and run the app on a local Android Emulator',
    '$ npx expo run:android --variant debug',
  ]}
  cmdCopy={null}
/>

The resulting app will be fully debuggable with Xcode or Android Studio, but will work with the EAS Update service the same way as a release build, and will not connect to a local packager.
